<template>
    <div>
        <button type="button" v-on:click="selectFile()" class="btn btn-white btn-default btn-round">
            <i class="ace-icon fa fa-upload"></i>
            {{text}}
        </button>
        <input class="hidden" type="file" ref="file" v-on:change="uploadFile()" v-bind:id="inputId+'-input'">
    </div>
</template>

<script>
    export default {
        name: 'oss-file-course',
        props: {
            text: {
                default: "上传大文件"
            },
            inputId: {
                default: "file-upload"
            },
            suffixs: {
                default: []
            },
            use: {
                default: ""
            },
            shardSize: {
                default: 5 * 1024 *1024
            },
            url: {
                default: "oss"
            },
            afterUpload: {
                type: Function,
                default: null
            },
        },
        data: function () {
            return {
                failInt: 0, // 失败次数
            }
        },
        methods: {

            uploadFile () {
                let _this = this;
                let file = _this.$refs.file.files[0];

                console.log(JSON.stringify(file));
                /*文件基本属性
                  name: "test.mp4"
                  lastModified: 1901173357457
                  lastModifiedDate: Tue May 27 2099 14:49:17 GMT+0800 (中国标准时间) {}
                  webkitRelativePath: ""
                  size: 37415970
                  type: "video/mp4"
                */

                //只要文件名或文件本身没有被更改，那么它的属性是不会被改变的，
                // 这里利用这个特性生成文件标识key，将其写入数据库记录，
                // 下一次传来的key还是一样的，就代表是同一个文件，后端完成断点续传。
                // 如果下一次传来的key不一样，说明文件不是之前那个，后端将数据作为新增记录写入数据库。

                //修复之前的bug，插件不支持直接传入文件变量，直接传入file变量的效果和传入空数组是一样的。
                console.log(hex_md5(file));
                console.log(hex_md5(Array()));

                //不再传入变量file，而是传入name,size,type。
                // 生成文件标识，标识用于区分多次上传的是不是同一个文件
                let key = hex_md5(file.name + file.size + file.type);//直接使用16进制的key也是可以的。

                //16进制太长了，为了让key的值短一点，下面转换一下。
                let key10 = parseInt(key, 16);
                let key62 = Tool._10to62(key10);
                console.log("key="+key);
                console.log("key10="+key10);
                console.log("key62="+key62);
                /*
                  d41d8cd98f00b204e9800998ecf8427e
                  2.8194976848941264e+38
                  6sfSqfOwzmik4A4icMYuUe
                 */


                // 判断文件格式
                let suffixs = _this.suffixs;
                let fileName = file.name;
                let suffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length).toLowerCase();
                let validateSuffix = false;
                for (let i = 0; i < suffixs.length; i++) {
                    if (suffixs[i].toLowerCase() === suffix) {
                        validateSuffix = true;
                        break;
                    }
                }
                if (!validateSuffix) {
                    Toast.warning("文件格式不正确！只支持上传：" + suffixs.join(","));
                    $("#" + _this.inputId + "-input").val("");
                    return;
                }

                // 文件分片
                // let shardSize = 10 * 1024 * 1024;    //以10MB为一个分片，1MB = 1024 * 1024Byte
                let shardSize = _this.shardSize;
                let shardIndex = 1;		//分片索引
                let size = file.size;
                //文件的中分片数=文件size/分片大小。
                //因为分片以1mb为单位，所以不能有小数，要向上取整。
                let shardTotal = Math.ceil(size/shardSize);
                let param = {
                    'id': "",
                    'shardIndex': shardIndex,
                    'shardSize': shardSize,
                    'shardTotal': shardTotal,
                    'use': _this.use,
                    'name': file.name,
                    'suffix': suffix,
                    'size': file.size,
                    'key': key62
                };

                _this.check(param);
            },


            check: function (param) {
                let _this = this;
                let path = process.env.VUE_APP_SERVER + '/file/'+_this.url+'/admin/check/';
                _this.$ajax.get(path + param.key).then((response)=>{
                    let resp = response.data;
                    if (resp.success) {
                        let obj = resp.content;
                        if (!obj) {//这里判断obj如果为空，就代表没有这个文件
                            param.shardIndex = 1;
                            console.log("没有找到文件记录，从分片1开始上传");
                            _this.upload(param);
                        } else if(obj.shardIndex == obj.shardTotal){
                            // 已上传分片 = 分片总数，说明已全部上传完，不需要再上传
                            Toast.success("文件极速秒传成功！");
                            _this.afterUpload(resp);
                            $("#" + _this.inputId + "-input").val("");
                        } else {//obj如果不为空，就代表这个文件之前上传过，找到之前上传的分片，+1再续传
                            param.shardIndex = obj.shardIndex + 1;
                            console.log("找到文件记录，从分片" + param.shardIndex + "开始上传");
                            _this.upload(param);
                        }
                    } else {
                        //分片检查失败，告知用户上传失败。
                        Toast.warning("文件上传失败");
                        $("#" + _this.inputId + "-input").val("");
                    }
                })
            },

            upload: function (param) {
                let _this = this;
                let shardIndex = param.shardIndex;
                let shardTotal = param.shardTotal;
                let shardSize = param.shardSize;
                let fileShard = _this.getFileShard(shardIndex, shardSize);

                //如果失败达到3次，则删除文件，告知用户上传失败。
                console.log("失败次数 = "+_this.failInt)
                if (_this.failInt >= 3) {
                    param.id = _this.failInt;
                }


                let formData = new window.FormData();
                formData.append('shard', fileShard);
                formData.append('param', JSON.stringify(param));

                // if (shardIndex == 3) {
                //     //测试断点续传，实际只上传了3个片段，直接满足递归条件提交到服务端。
                //     shardIndex = 9;
                // }

                console.log("上传分片：", shardIndex);

                Progress.show(parseInt((shardIndex - 1) * 100 / shardTotal));

                let path = process.env.VUE_APP_SERVER + '/file/'+ _this.url+'/admin/upload';
                _this.$ajax.post(path, formData).then((response) => {
                    let resp = response.data;

                    if (resp.success == false) {
                        _this.failInt = 0;
                        Loading.show();
                        Toast.warning("文件上传失败");
                        Progress.hide();
                        $("#" + _this.inputId + "-input").val("");
                        Loading.hide();
                        return ;
                    }
                    console.log("上传分片:"+shardIndex+" 成功：", resp);
                    Progress.show(parseInt(shardIndex * 100 / shardTotal));

                    //如果网络异常，上传失败，则重试。
                    if (resp.code != 0) {
                        console.log("上传分片失败 = "+resp.code)
                        _this.failInt = _this.failInt + 1;
                        Toast.warning("上传失败" + _this.failInt + "次，重新连接。");
                        this.check(param);
                        return ;

                    // 上传结束时，做个判断，
                    // 如果shardIndex == shardTotal，说明全部分片已经上传完毕，
                    // 如果shardIndex < shardTotal，说明分片还没全部上传完毕，继续上传一下个分片。
                    } else if (shardIndex < shardTotal) {
                        //上传一下个分片
                        param.shardIndex = param.shardIndex + 1;
                        _this.upload(param);
                    } else {
                        Progress.hide();
                        _this.afterUpload(resp);
                        $("#" + _this.inputId + "-input").val("");
                        //当全部分片上传完毕后，可以在这里发起一个新的请求
                        //通知服务端全部分片上传完毕，可以合并所有已经上传的分片。
                        //但这个功能不用发起请求，服务端也可以自动完成，这里就交给服务端处理。
                    }
                    // js的循环递归的原因，
                    // 递归没有完成，下面这个语句也会被执行，导致file变量被置空
                    // 从而报file is not defind错误。必须将其放到else中才能正常运行。
                    //$("#" + _this.inputId + "-input").val("");
                });

            },

            getFileShard: function (shardIndex, shardSize) {
                let _this = this;
                let file = _this.$refs.file.files[0];
                let start = (shardIndex - 1) * shardSize;	//当前分片起始位置
                //当前分片结束位置=起点+分片大小end = start + shardSize
                //因为最后一段是不足一个分片大小的，而分片却是以1mb为单位，
                //加上起点后，end的数值就会比文件size还大，所以end = start + shardSize需要优化。
                /*比如1.5mb的文件，开始上传：
                 * 第一段上传：起点start=0,结束点end=1，上传了1mb，
                 * 第二段上传：起点start=1,结束点end=2，上传了1mb，
                 * 两段上传完毕后，文件大小就变成了2mb。这是不对的，
                 * 所以当end大于文件size时，就应该以文件的size为结束值
                 * 最后一段的end值应该是文件的size。
                 * 通过js函数Math.min(值0，值1)先取小的那个，可以解决最后一段的问题。
                 */
                let end = Math.min(file.size, start + shardSize);
                let fileShard = file.slice(start, end); //从文件中截取当前的分片数据
                return fileShard;
            },

            selectFile () {
                let _this = this;
                $("#" + _this.inputId + "-input").trigger("click");
            }
        }
    }
</script>
